#include "Position.h"

Position::Position()
{
    board = new Board();
    gameStateStack = new Stack<GameState>();
}

Position(ref Position position)
{
    board = new Board(position.board);
    gameStateStack = position.gameStateStack;
}

Position(string strPos)
{
    board = new Board();
    gameStateStack = new Stack<GameState>();
    SetFromFEN(strPos);
}

void SetFromFEN(String fenString)
{
    board.Clear();
    gameStateStack.Clear();
    int square = 56;

    string[] parameters = fenString.Split(' ');
    String strBoard = parameters[0];
    char ply = parameters[1][0];
    String castling = parameters[2];
    String enPassant = parameters[3];
    String halfMoveClock = parameters[4];
    String fullMoveNumber = parameters[5];

    foreach (char carBoard in strBoard)
    {
        switch (carBoard)
        {
            case 'K':
                board.SetPiece(Piece.W_KING, square);
                square++;
                break;
            case 'Q':
                board.SetPiece(Piece.W_QUEEN, square);
                square++;
                break;
            case 'R':
                board.SetPiece(Piece.W_ROOK, square);
                square++;
                break;
            case 'N':
                board.SetPiece(Piece.W_KNIGHT, square);
                square++;
                break;
            case 'B':
                board.SetPiece(Piece.W_BISHOP, square);
                square++;
                break;
            case 'P':
                board.SetPiece(Piece.W_PAWN, square);
                square++;
                break;
            case 'k':
                board.SetPiece(Piece.B_KING, square);
                square++;
                break;
            case 'q':
                board.SetPiece(Piece.B_QUEEN, square);
                square++;
                break;
            case 'r':
                board.SetPiece(Piece.B_ROOK, square);
                square++;
                break;
            case 'n':
                board.SetPiece(Piece.B_KNIGHT, square);
                square++;
                break;
            case 'b':
                board.SetPiece(Piece.B_BISHOP, square);
                square++;
                break;
            case 'p':
                board.SetPiece(Piece.B_PAWN, square);
                square++;
                break;
            case '/':
                square -= 16;
                break;
            case '1':
                square += 1;
                break;
            case '2':
                square += 2;
                break;
            case '3':
                square += 3;
                break;
            case '4':
                square += 4;
                break;
            case '5':
                square += 5;
                break;
            case '6':
                square += 6;
                break;
            case '7':
                square += 7;
                break;
            case '8':
                square += 8;
                break;
            default:
                // TODO Throw incorrect data exception
                break;
        }
    }

    GameState gstate = new GameState();
    switch (ply)
    {
        case 'w':
            gstate.turn = WHITE;
            break;
        case 'b':
            gstate.turn = BLACK;
            break;
        default:
            // TODO Throw incorrect data exception
            break;
    }

            
    gstate.whiteCanCastleKingSide = false;
    gstate.whiteCanCastleQueenSide = false;
    gstate.blackCanCastleKingSide = false;
    gstate.blackCanCastleQueenSide = false;
    foreach (char carCastle in castling)
    {
        switch (carCastle)
        {
            case 'K':
                gstate.whiteCanCastleKingSide = true;
                break;
            case 'Q':
                gstate.whiteCanCastleQueenSide = true;
                break;
            case 'k':
                gstate.blackCanCastleKingSide = true;
                break;
            case 'q':
                gstate.blackCanCastleQueenSide = true;
                break;
            default:
                // TODO Throw incorrect data exception
                break;
        }

    }

    if (enPassant[0] != '-')
    {
        gstate.lastEnPassant = (enPassant[0] - 'a') + 8 * (enPassant[1] - '1');
    }

    gstate.halfMoveClock = int.Parse(halfMoveClock);
    gstate.fullMoveNumber = int.Parse(fullMoveNumber);

    gameStateStack.Push(gstate);
}

bool IsWhiteToMove()
{
    return gameStateStack.Peek().turn;
}

bool IsBlackToMove()
{
    return !gameStateStack.Peek().turn;
}
        
bool GetTurn()
{
    return gameStateStack.Peek().turn;
}

/**
    * Indicates if the piece is playable for the given player (only check the
    * color)
    * 
    * @param square
    * @return
    */
bool IsPiecePlayable(int square)
{
    Piece pieceCode = board.GetPiece(square);
    if (GetTurn())
    {
        return IsPieceWhite(pieceCode);
    }
    else
    {
        return IsPieceBlack(pieceCode);
    }
}

bool IsPieceWhite(Piece piece)
{
    return (piece == Piece.W_PAWN || piece == Piece.W_ROOK || piece == Piece.W_KNIGHT
            || piece == Piece.W_BISHOP || piece == Piece.W_QUEEN || piece == Piece.W_KING);
}

bool IsPieceBlack(Piece piece)
{
    return (piece == Piece.B_PAWN || piece == Piece.B_ROOK || piece == Piece.B_KNIGHT
            || piece == Piece.B_BISHOP || piece == Piece.B_QUEEN || piece == Piece.B_KING);
}

/**
    * Return if the square is empty or occupied by a piece of the opponent
    * 
    * @param square
    * @return
    */
bool IsEmptyOrOpponent(int square)
{
    Piece piece = board.GetPiece(square);
    if (piece == Piece.NO_PIECE) return true;
    if (this.IsWhiteToMove()) return IsPieceBlack(piece);
    //if (this.IsBlackToMove()) return IsPieceWhite(piece);
    return IsPieceWhite(piece); //optim
}
bool IsEmptyOrColor(int square, bool color)
{
    Piece piece = this.GetPiece(square);
    if (piece == Piece.NO_PIECE) return true;
    if (color) return IsPieceWhite(piece);
    return IsPieceBlack(piece);
}

Piece GetPiece(int square)
{
    return board.GetPiece(square);
}


// Perform the move, augment gameStack and change turn
void Play(Move m)
{
    Debug.Assert(this.gameStateStack.Count >= 1);
    Piece piece = m.piece;
    Piece cPiece = m.cPiece;
            
    GameState newGameState = new GameState();

    // En-passant extra-move (remove opponant's pawn)
    if (cPiece == Piece.NO_PIECE && Square.GetCol(m.from) != Square.GetCol(m.to))
    {
        if (piece == Piece.W_PAWN) board.SetPiece(Piece.NO_PIECE, m.to - 8);
        if (piece == Piece.B_PAWN) board.SetPiece(Piece.NO_PIECE, m.to + 8);
    }

    // Castleling extra-move (Rook)
    if (piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_G1) 
        board.Move(Square.SQ_H1, Square.SQ_F1);
    if (piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_C1) 
        board.Move(Square.SQ_A1, Square.SQ_D1);
    if (piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_G8) 
        board.Move(Square.SQ_H8, Square.SQ_F8);
    if (piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_C8) 
        board.Move(Square.SQ_A8, Square.SQ_D8);

    // Castleling - canceller
    // TODO: find a clean and quick way to update this status
    if (piece == Piece.W_KING)
    {
        newGameState.whiteCanCastleKingSide = false;
        newGameState.whiteCanCastleQueenSide = false;
    }
    else
        if (piece == Piece.B_KING)
        {
            newGameState.blackCanCastleKingSide = false;
            newGameState.blackCanCastleQueenSide = false;
        }
        else
            if (piece == Piece.W_ROOK && m.from == Square.SQ_A1)
            {
                newGameState.whiteCanCastleQueenSide = false;
            }
            else
                if (piece == Piece.W_ROOK && m.from == Square.SQ_H1)
                {
                    newGameState.whiteCanCastleKingSide = false;
                }
                else
                    if (piece == Piece.B_ROOK && m.from == Square.SQ_A8)
                    {
                        newGameState.blackCanCastleQueenSide = false;
                    }
                    else
                        if (piece == Piece.B_ROOK && m.from == Square.SQ_H8)
                        {
                            newGameState.blackCanCastleKingSide = false;
                        }

    // keeping en-passant last move
    if ((piece == Piece.W_PAWN && (Square.GetRow(m.to) - Square.GetRow(m.from)) == 2)
        || (piece == Piece.B_PAWN && (Square.GetRow(m.to) - Square.GetRow(m.from)) == 2))
    {
        newGameState.lastEnPassant = Math.Max(m.from, m.to) - Math.Min(m.from, m.to);
    }
    else
    {
        newGameState.lastEnPassant = Square.NO_SQUARE;
    }

    // standard move
    board.Move(m.from, m.to);

    // promotion
    bool turn = gameStateStack.Peek().turn;
    if (m.promotion != '0')
    {
        switch (m.promotion)
        {
            case 'q':
                if (turn) this.SetPiece(Piece.W_QUEEN, m.to);
                else this.SetPiece(Piece.B_QUEEN, m.to);
                break;
            case 'r':
                if (turn) this.SetPiece(Piece.W_ROOK, m.to);
                else this.SetPiece(Piece.B_ROOK, m.to);
                break;
            case 'b':
                if (turn) this.SetPiece(Piece.W_BISHOP, m.to);
                else this.SetPiece(Piece.B_BISHOP, m.to);
                break;
            case 'n':
                if (turn) this.SetPiece(Piece.W_KNIGHT, m.to);
                else this.SetPiece(Piece.B_KNIGHT, m.to);
                break;
        }

    }


    newGameState.turn = !turn;
    // 50 plies rule
    if (cPiece != Piece.NO_PIECE
        || piece == Piece.W_PAWN
        || piece == Piece.B_PAWN)
        newGameState.halfMoveClock = 0;
    else
        newGameState.halfMoveClock = gameStateStack.Peek().halfMoveClock + 1;
    // full move clock (+1 only after black ply)
    if (turn)
        newGameState.fullMoveNumber = gameStateStack.Peek().fullMoveNumber;
    else
        newGameState.fullMoveNumber = gameStateStack.Peek().fullMoveNumber + 1;

    // store pseudFen for 3-times repetition rule
    newGameState.pseudoFen = this.GetPseudoFEN();

    // push the new state in the gameStateStack
    gameStateStack.Push(newGameState);
}

void Unplay(Move m)
{
    Debug.Assert(this.gameStateStack.Count >= 2);
    // Unplay - base part
    board.SetPiece(m.piece,m.from);
    board.SetPiece(m.cPiece, m.to);

    // recover pre-casteling position
    if (m.piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_G1) 
        board.Move(Square.SQ_F1, Square.SQ_H1);
    if (m.piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_C1) 
        board.Move(Square.SQ_D1, Square.SQ_A1);
    if (m.piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_G8) 
        board.Move(Square.SQ_F8, Square.SQ_H8);
    if (m.piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_C8) 
        board.Move(Square.SQ_D8,Square.SQ_A8);

    // recover en-passant captured pawns
    // TODO: improve this code
    if (m.cPiece == Piece.NO_PIECE && Square.GetCol(m.from) != Square.GetCol(m.to))
    {
        if (m.piece == Piece.W_PAWN) board.SetPiece(Piece.B_PAWN, m.to - 8);
        if (m.piece == Piece.B_PAWN) board.SetPiece(Piece.W_PAWN, m.to + 8);
    }

    // Pop the state to return to the n-1 game state
    gameStateStack.Pop();
}

// TODO improve this method to implement the full do/undo move
void Move(byte from, byte to)
{
    board.Move(from,to);
}

void SetPiece(Piece piece, int square)
{
    board.SetPiece(piece,square);
}

void SetStartPosition()
{
    this.SetFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
}

// TODO impove this method
String GetSimplifiedHashCode()
{
    /*
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < 64; i++)
    {
            result.Append((int)pieces[i]);
        }
                
    return result.ToString();*/
    return this.GetPseudoFEN();
}


private static char[] fenPieceCodes = { 'P', 'N', 'B', 'R', 'Q', 'K', 'p', 'n', 'b', 'r', 'q', 'k' };
//private string strPos;

/*
    * Unique key generator for positions
    */
String GetPseudoFEN()
{
    StringBuilder result = new StringBuilder();
    int emptySquares = 0;
    for (int i = 0; i < 64; i++)
    {
        Piece p = board.pieces[i];
        if (p == Piece.NO_PIECE)
        {
            emptySquares++;
        }
        else
        {
            if (emptySquares > 0)
            {
                result.Append(emptySquares);
                emptySquares = 0;
            }
            result.Append(fenPieceCodes[(int)p]);

        }
    }
    GameState gs = this.gameStateStack.Peek();
    result.Append(gs.turn);
    result.Append(gs.whiteCanCastleQueenSide ? '1' : '0');
    result.Append(gs.whiteCanCastleKingSide ? '1' : '0');
    result.Append(gs.blackCanCastleQueenSide ? '1' : '0');
    result.Append(gs.blackCanCastleKingSide ? '1' : '0');

    // TODO: see if this line is not too much restrictive
    // depth (= limit TT application to equal depth)
    //result.Append(gameStateStack.Count);

    return result.ToString();
}


internal bool IsSquareEmpty(int sq)
{
    return board.IsSquareEmpty(sq);
}

internal bool WhiteCanCastleKingSide()
{
    return gameStateStack.Peek().whiteCanCastleKingSide;
}

internal bool BlackCanCastleKingSide()
{
    return gameStateStack.Peek().blackCanCastleKingSide;
}

internal bool WhiteCanCastleQueenSide()
{
    return gameStateStack.Peek().whiteCanCastleQueenSide;
}

internal bool BlackCanCastleQueenSide()
{
    return gameStateStack.Peek().blackCanCastleQueenSide;
}

internal int LastEnPassant()
{
    return gameStateStack.Peek().lastEnPassant;
}

// for test only !!!
bool IsEqual(Position pos)
{
    return board.IsEqual(pos.board) && IsEqual(gameStateStack,pos.gameStateStack);
}

// for test only !!!
private static bool IsEqual(Stack<GameState> s1, Stack<GameState> s2)
{
    Stack<GameState> st1 = new Stack<GameState>(s1); 
    Stack<GameState> st2 = new Stack<GameState>(s2); 

    while(st1.Count>0 && st2.Count>0)
    {
        GameState g1 = st1.Pop();
        GameState g2 = st2.Pop();
        if (!g1.IsEqual(g2)) return false;
    }

    return (st1.Count == 0) && (st2.Count == 0);
}

internal int Evaluate(ref int[] coeff)
{
    return board.Evaluate(ref coeff);
}


internal int GetPlayedMoveNumber()
{
    return gameStateStack.Count-1;
}

int GetMovesWithoutCaptureOrPawnPush()
{
    return gameStateStack.Peek().halfMoveClock;
}

internal object GetGameStateStack()
{
    return gameStateStack;
}

internal bool IsThreeTimesRepetitionDraw()
{
    Debug.Assert(gameStateStack.Count >= 6);
    GameState[] ags = gameStateStack.ToArray<GameState>();

    int top = ags.Length-1;

    return (ags[top].pseudoFen == ags[top - 2].pseudoFen)
        && (ags[top - 2].pseudoFen == ags[top - 4].pseudoFen);
}
}